package de.fzi.kasma.learner.path;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.openrdf.model.vocabulary.RDF;

import edu.unika.aifb.graphindex.data.Table;
import edu.unika.aifb.graphindex.index.IndexDirectory;
import edu.unika.aifb.graphindex.index.IndexReader;
import edu.unika.aifb.graphindex.index.PatternIndex;
import edu.unika.aifb.graphindex.query.StructuredQuery;
import edu.unika.aifb.graphindex.searcher.structured.VPEvaluator;

public class PathRefinement {

	private static int m_varcount = 0;

	private IndexReader m_idxReader;
	private PatternIndex m_pi;

	private static final Logger log = Logger.getLogger(PathRefinement.class);

	public PathRefinement(IndexReader ir) throws Exception {
		this.m_idxReader = ir;
		this.m_pi = new PatternIndex(ir);

	}

	/**
	 * The method getExtendedPaths returns the possible extentions of the
	 * starting graph.Each time the pattern will be extended by only one node.
	 * 
	 * @param path
	 *            The starting path graph of the refinement function.
	 * @return PathGraph [] paths an array of possible path graphs refined from
	 *         the starting path graph.
	 * @throws Exception
	 */

	public PathGraph[] getExtendedPaths(PathGraph path) throws Exception {
		// The possible refined paths will be stored in List <PathGraph> paths
		// first.
		List<PathGraph> paths = new ArrayList<PathGraph>();

		// The type relations of the path graph
		List<PathEdge> typeEdges = path.getEdges(RDF.TYPE.stringValue());
		for (PathEdge edge : typeEdges) {
			// Assuming each type relation has an object, the list objProps
			// holds the possible properties of this object.
			List<String> objProps = m_pi.getObjectProperties(edge.getTarget()
					.getLabel());

			for (String objProp : objProps) {
				// String o = the label of the object in type relation.
				String o = edge.getTarget().getLabel();
				// Assuming each pattern has a subject , the list subjs holds
				// the possible subjects for every incoming edge of an object.
				List<String> subjs = m_pi.getPatternSubjects(objProp, o);
				for (String subj : subjs) {
					if (!path.containsPattern(subj, objProp, true, edge)) {
						// PathGraph newGraph = basically the copy of the old
						// starting graph, which the refined nodes will be added
						// into.
						PathGraph newGraph = path.deepCopy();
						// PNode newNode = used for generating new node for each
						// refined graph.
						PNode newNode = new PNode("?v" + m_varcount);
						m_varcount++;

						PNode type = new PNode(subj);

						newGraph.addEdge(newNode, objProp, edge.getSource());
						newGraph.addEdge(newNode, RDF.TYPE.stringValue(), type);
						// Add the refined graph in possible paths list.
						paths.add(newGraph);
					}
				}
			}
			// Assuming each type relation has a subject, the list subjProps
			// holds the possible properties of this subject(taking the object
			// of the type relation in process).
			List<String> subjProps = m_pi.getSubjectProperties(edge.getTarget()
					.getLabel());
			for (String subProp : subjProps) {
				// String s = the label of the subject in type relation.
				String o = edge.getTarget().getLabel();
				// Assuming each pattern has an object , the list objects holds
				// the possible objects for every outgoing edge of a subject.
				List<String> objects = m_pi.getPatternObjects(subProp, o);
				for (String objs : objects) {
					if (!path.containsPattern(objs, subProp, false, edge)) {
						PathGraph newGraph = path.deepCopy();

						PNode newNode = new PNode("?v" + m_varcount);
						m_varcount++;
						PNode type = new PNode(objs);

						newGraph.addEdge(edge.getSource(), subProp, newNode);
						newGraph.addEdge(newNode, RDF.TYPE.stringValue(), type);
						paths.add(newGraph);

					}
				}
			}
		}
		return paths.toArray(new PathGraph[0]);
	}

	public void getPathGraphStats(PathGraph path) {

	}

	/**
	 * The method getGroundTerms returns the possible candidates of a node in a
	 * refined PathGraph.
	 * 
	 * @param path
	 *            The starting PathGraph
	 * @return List <GroundTerm> [] a list of a possible ground terms in a
	 *         refined PathGraph.The length of array will be defined through
	 *         variables size.
	 * @throws Exception
	 */
	public List<GroundTerm>[] getGroundTerms(PathGraph path) throws Exception {
		// The variables of an edge in a PathGraph will be stored in
		// List<String> variables.
		List<String> variables = new ArrayList<String>();
		// create a structured query
		StructuredQuery q = new StructuredQuery("q1");
		for (PathEdge edge : path.edgeSet()) {

			// All edges of the PathGraph will be added in Structured Query.
			q.addEdge(edge.getSource().getLabel(), edge.getProperty(), edge
					.getTarget().getLabel());
			if (edge.getSource().isVariable()
					&& !variables.contains(edge.getSource().getLabel()))
				variables.add(edge.getSource().getLabel());
			if (edge.getTarget().isVariable()
					&& !variables.contains(edge.getTarget().getLabel()))
				variables.add(edge.getTarget().getLabel());
			if (edge.getProperty().equals(RDF.TYPE.toString())) {
				if (edge.getSource().isEntity()) {
					continue;
				}
			}
			if (edge.getSource().isEntity()) {
				continue;
			}
		}
		for (String var : variables)
			// All variables will be selected as starting node.
			q.setAsSelect(var);

		VPEvaluator eval = new VPEvaluator(m_idxReader);
		// All of the possible candidates will be stored in Table<String>
		// entities.
		Table<String> entities = eval.evaluate(q);

		// List <GroundTerm> [] result holds all of the possible candidates of
		// the new node of refined PathGraph.The array length will be decided
		// through variables size.
		List<GroundTerm>[] result = new ArrayList[variables.size()];
		// The columns of entites table.
		int[] columns = new int[variables.size()];

		for (int i = 0; i < variables.size(); i++) {
			// Create a new array for every variable possible.
			result[i] = new ArrayList<GroundTerm>();
			// Gets the column of possible candidates.
			columns[i] = entities.getColumn(variables.get(i));

		}

		for (int i = 0; i < variables.size(); i++) {
			for (int k = 0; k < entities.rowCount(); k++) {
				// Gets the values of the candidates.
				String[] row = entities.getRow(k);
				// v = candidate.
				String v = row[columns[i]];
				// If the list does not contain the ground term, it will be
				// added,and the value of the newly added ground term will be
				// one.
				if (!result[i].contains(new GroundTerm(v, 0, variables.get(i)))) {
					result[i].add(new GroundTerm(v, 1, variables.get(i)));
				} else {
					// If the list already got the ground term, its value will
					// be increased by one.
					int idx = result[i].indexOf(new GroundTerm(v, 1, variables
							.get(i)));
					result[i].get(idx).count++;

				}
			}
		}

		for (int i = 0; i < variables.size(); i++) {
			// Sorts the list in descending order.
			Collections.sort(result[i], new Comparator<GroundTerm>() {
				// Compare the values of ground terms.
				public int compare(GroundTerm o1, GroundTerm o2) {
					if (o1 == null) {
						return (o2 == null) ? 0 : 1;
					} else if (o1.count < o2.count) {
						return 1;
					} else if (o1.count > o2.count) {
						return -1;
					} else {
						return 0;
					}
				}
			});

		}

		// Resizing the list in order to get 10 top ground terms out of the
		// result list.
		List<GroundTerm>[] resizedList = new ArrayList[variables.size()];
		for (int i = 0; i < variables.size(); i++) {
			resizedList[i] = new ArrayList<GroundTerm>();
			for (int k = 0; k < result[i].size(); k++) {
				if (k < 10) {
					resizedList[i].add(result[i].get(k));
				}
			}
		}

		// for (GroundTerm key : resizedList[0]) {
		// System.out.println(key.uri + " " + key.count + " " + key.varName);
		//
		// }
		return resizedList;

	}

	private static void removeDuplicates(PathGraph[] paths) {
		for (int i = 0; i < paths.length; i++) {
			for (int k = 0; k < i; k++) {
				if (paths[i].equals(paths[k])) {
					paths[k] = null;
				}
			}
		}
	}

	public class GroundTerm {
		public String uri;

		public int count;

		public String varName;

		public GroundTerm(String uri, int c, String var) {
			this.uri = uri;
			this.count = c;
			this.varName = var;
		}

		@Override
		public boolean equals(Object obj) {
			// TODO Auto-generated method stub
			return uri.equals(((GroundTerm) obj).uri);
		}

	}

	public static void main(String[] args) throws Exception {

		IndexReader ir = new IndexReader(new IndexDirectory("data/halfminset"));
		PathGraph q = new PathGraph();
		q.addEdge("?u", "http://www.movie.org/ontology.owl#like", "?m");
		q.addEdge("?m", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
				"http://www.movie.org/ontology.owl#Movie");
		q.addEdge("?u", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
				"http://www.movie.org/ontology.owl#User");
		q.addEdge("http://www.movie.org/resource/21",
				"http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
				"http://www.movie.org/ontology.owl#Age");
		q.addEdge("?u", "http://www.movie.org/ontology.owl#hasAge",
				"http://www.movie.org/resource/21");
		q.addEdge("http://www.movie.org/resource/Dan",
				"http://www.movie.org/ontology.owl#hasAge", "?v130");
		q.addEdge("?v130", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
				"http://www.movie.org/ontology.owl#Age");

		PathRefinement ref = new PathRefinement(ir);
		PathGraph[] paths = ref.getExtendedPaths(q);
		for (int p = 0; p < paths.length; p++) {
			// System.out.println(paths[p]);
			Set<PathEdge> dset = paths[p].edgeSet();
			for (PathEdge e : dset) {
				System.out.println(e.getSource().getLabel().toString() + " "
						+ e.getProperty() + " " + e.getTarget() + " ; ");

			}
			List<GroundTerm>[] candidates = ref.getGroundTerms(paths[p]);
			for (int k = 0; k < candidates.length; k++)
				for (GroundTerm key : candidates[k]) {
					System.out.println(key.uri + " " + key.count + " "
							+ key.varName);

			}
		}
		// for (int i = 0; i < paths.length; i++) {
		// List<GroundTerm>[] candidates = ref.getGroundTerms(paths[i]);
		// for (int k = 0; k < candidates.length; k++)
		// for (GroundTerm key : candidates[k]) {
		// System.out.println(key.uri + " " + key.count + " "
		// + key.varName);
		//
		// }
		// }
	}
}